/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab-modules.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include "Ogre_Gfx.h"
#include <dylab/simulation/Simulation.h>
#include <dylab/utils/exceptions/EInvalidState.h>
#include <dylab/utils/exceptions/EDoesNotExists.h>

//===========================================================================================
const dylab::String dylab::Ogre_Gfx::COMPONENT_NAME = "ogre";

//===========================================================================================
dylab::Ogre_Gfx::Ogre_Gfx() 
: ComGfx(COMPONENT_NAME), mRoot(NULL), mMeshManager(NULL)
, mSceneMap(true), mGuiSystemMap(true)
, mDefaultScene(NULL), mDefaultWindow(NULL), mDefaultGuiSystem(NULL)
{
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::~Ogre_Gfx() 
{
    mGuiSystemMap.removeAll();
    mSceneMap.removeAll();    

    if (mMeshManager != NULL)
        delete mMeshManager;
    if (mRoot != NULL)
        delete mRoot;
    if (mD3D9Plugin != NULL)
        delete mD3D9Plugin;
    if (mGLPlugin != NULL)
        delete mGLPlugin;
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::messageLogged(const Ogre::String & message, Ogre::LogMessageLevel lml, bool maskDebug, const Ogre::String & logName)
{
    DYLAB_LOGF("Ogre>> %1%", message);
}
//-----------------------------------------------------------------------------
dylab::String dylab::Ogre_Gfx::getDescription() const
{    
    // build ogre description String
    return String::fmt("%1% %2%.%3%.%4% %5%", getFullName(), OGRE_VERSION_MAJOR, OGRE_VERSION_MINOR, OGRE_VERSION_PATCH, OGRE_VERSION_NAME);
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::initialize(Simulation * _parentSimulation)
{
    // check argument
    DYLAB_ASSERT(_parentSimulation != NULL);
    mParentSimulation = _parentSimulation;

    try {        
        // create Ogre log without loggin to a file
        Ogre::LogManager * logManager = new Ogre::LogManager();
        DYLAB_ASSERT(logManager != NULL);
        logManager->createLog("dylab", true, true, true)->addListener(this);

        // create Ogre Ogre::Root object
        mRoot = new Ogre::Root(String::EMPTY, String::EMPTY);
        DYLAB_ASSERT(mRoot != NULL);

        // create & install the directx 9 render system plugin
        mD3D9Plugin = new Ogre::D3D9Plugin();
        DYLAB_ASSERT(mD3D9Plugin != NULL);
        mRoot->installPlugin(mD3D9Plugin);

        // create & install the opengl render system plugin
        mGLPlugin = new Ogre::GLPlugin();
        DYLAB_ASSERT(mGLPlugin != NULL);
        mRoot->installPlugin(mGLPlugin);

        mMeshManager = new MeshManager();
        DYLAB_ASSERT(mMeshManager != NULL);

        // start the timer
        mTimer.reset();
    }
    catch (Ogre::Exception & e) {
        throw EOperationFailed(String::fmt("Ogre gfx setup failed: %1%", e.getFullDescription()));
    }
    catch (std::exception & e) {
        throw EOperationFailed(String::fmt("Ogre gfx setup failed: %1%", String(e.what())));
    }
    catch (...) {
        throw EOperationFailed(String::fmt("Ogre gfx setup failed."));
    }
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::loadFromPropertyTree(const PropertyTree & _pt)
{
    // setup ogre render systems
    for (PropertyTree::NodeList::ConstIterator renderSys(*_pt.getNodeList("renderSystem")); renderSys.isValid(); renderSys++)
    {
        for (PropertyTree::NodeListMap::ConstIterator renderParam(renderSys->getChildNodeListMap()); renderParam.isValid(); renderParam++)
            setRenderSystemParam(renderSys->getValue(), ~renderParam, renderParam->getFirst()->getValue());
    }
    setActiveRenderSystem(_pt.getNodeValue("activeRenderSystem"));

    // setup ogre resources
    for (PropertyTree::NodeList::ConstIterator resGroup(*_pt.getNodeList("resourceGroup")); resGroup.isValid(); resGroup++)
    {
        for (PropertyTree::NodeListMap::ConstIterator resArchive(resGroup->getChildNodeListMap()); resArchive.isValid(); resArchive++)
            for (PropertyTree::NodeList::ConstIterator resName(*resArchive()); resName.isValid(); resName++)
                addResourceArchive(resGroup->getValue(), ~resArchive, resName->getValue());
    }

    // create rendering window (only one supported so far)
    const PropertyTree::Node * window = _pt.getNode("window");
    createWindow(window->getChildNodeValue("title", "Dylab window"))
        ->setPosition(window->getChildNodeValueT("position", Vector2(240, 0)));

    // initialise resource groups (TODO: reposition, after moving Ogre::Root initialisation elsewhere from main window creation)
    initAllResourceGroups();

    // create meshes
    for (PropertyTree::NodeList::ConstIterator mesh(*_pt.getNodeList("mesh")); mesh.isValid(); mesh++)
    {
        // only plane mesh supported so far
        if (mesh->getChildNodeValue("type", "generic") == "plane")
        {
            // parse plane attributes
            Plane plane(mesh->getChildNodeValueT<Vector3>("normal"), mesh->getChildNodeValueT<real_t>("displacement"));
            Vector2 size(mesh->getChildNodeValueT<Vector2>("size"));
            Vector2 segments(mesh->getChildNodeValueT<Vector2>("segments"));
            Vector2 tiles(mesh->getChildNodeValueT<Vector2>("tiles"));

            getMeshManager()->createPlane(mesh->getValue(), plane, size, segments, tiles);
        }
    }

    // create scenes
    for (PropertyTree::NodeList::ConstIterator scene(*_pt.getNodeList("scene")); scene.isValid(); scene++)
    {
        // create the scene
        ComGfx::IScene * iScene = createScene(scene->getValue());

        // parse scene attributes
        iScene->setAmbientLight(scene->getChildNodeValueT("ambientLight", Colour::BLACK));
        iScene->setShadowType(scene->getChildNodeValueT("shadowType", ComGfx::IScene::ST_NONE));
        iScene->showBoundingBoxes(scene->getChildNodeValueT("showBoundingBoxes", false));

        // create scene entities
        for (PropertyTree::NodeList::ConstIterator entity(*scene->getChildNodeList("entity")); entity.isValid(); entity++)
        {
            // create the entity
            ComGfx::IEntity * iEntity 
                = iScene->createEntity(entity->getValue(), entity->getChildNodeValue("mesh"));
            
            // parse entity attributes
            iEntity->rotate(entity->getChildNodeValueT("rotateAxis", Vector3::UNIT_X), 
                            entity->getChildNodeValueT("rotateDeg", DYLAB_R(0)));
            iEntity->translate(entity->getChildNodeValueT("translate", Vector3::ZERO));
            iEntity->scale(entity->getChildNodeValueT("scale", Vector3(1, 1, 1)));
            iEntity->setCastShadows(entity->getChildNodeValueT("castShadows", false));
            iEntity->setMaterialName(entity->getChildNodeValue("material", "dylab/Chessboard"));
            iEntity->setRayQueryFlags(entity->getChildNodeValueT("queryFlags", 0));
        }

        // create scene lights
        for (PropertyTree::NodeList::ConstIterator light(*scene->getChildNodeList("light")); light.isValid(); light++)
        {
            // create the light
            ComGfx::ILight * iLight 
                = iScene->createLight(light->getValue(), light->getChildNodeValueT("type", ComGfx::ILight::DIRECTIONAL));
            
            // parse light attributes
            iLight->setPosition(light->getChildNodeValueT("position", Vector3::ZERO));
			iLight->setDirection(light->getChildNodeValueT("direction", Vector3::ZERO));
            iLight->setDiffuseColour(light->getChildNodeValueT("diffuse", Colour::WHITE));
            iLight->setSpecularColour(light->getChildNodeValueT("specular", Colour::BLACK));
            iLight->setCastShadows(light->getChildNodeValueT("castShadows", true));
            Vector3 attenCoeffs = light->getChildNodeValueT("attenuationCoeffs", Vector3(DYLAB_R(0.1), DYLAB_R(0.0001), DYLAB_R(0.00001)));
            iLight->setAttenuation(light->getChildNodeValueT("attenuationRange", DYLAB_R(1024)),
                                   attenCoeffs[0], attenCoeffs[1], attenCoeffs[2]);
            
        }

        // create scene cameras
        for (PropertyTree::NodeList::ConstIterator camera(*scene->getChildNodeList("camera")); camera.isValid(); camera++)
        {
            // create the camera
            ComGfx::ICamera * iCamera = iScene->createCamera(camera->getValue());
            
            // parse camera attributes
            iCamera->setPosition(camera->getChildNodeValueT("position", Vector3::ZERO));
            iCamera->lookAt(camera->getChildNodeValueT("lookAt", Vector3::UNIT_Z));
            iCamera->setNearClipDistance(camera->getChildNodeValueT("nearClipDistance", DYLAB_R(0.1)));
            iCamera->setFarClipDistance(camera->getChildNodeValueT("farClipDistance", DYLAB_R(0)));
        }
    }

    // create viewports
    for (PropertyTree::NodeList::ConstIterator viewport(*_pt.getNodeList("viewport")); viewport.isValid(); viewport++)
    {
        // determine the viewport window & scene name
        String windowName = viewport->getChildNodeValue("window", String::EMPTY);
        String sceneName = viewport->getChildNodeValue("scene", String::EMPTY);

        // determine the viewport camera
        ComGfx::ICamera * iVPCam = getScene(sceneName)
            ->getCamera(viewport->getChildNodeValue("camera", String::EMPTY));

        // retrieve the viewport relative position & dimension
        Vector2 position = viewport->getChildNodeValueT("position", Vector2::ZERO);
        Vector2 size = viewport->getChildNodeValueT("size", Vector2(1, 1));
        
        // create the viewport
        ComGfx::IViewport * iViewport = getWindow(/*windowName*/)
            ->createViewport(viewport->getValue().parse(0), iVPCam, position, size);

        // parse viewport attributes
        iViewport->setBackgroundColour(viewport->getChildNodeValueT("backgroundColour", Colour::BLACK));
    }

    // create rayQueries
    for (PropertyTree::NodeList::ConstIterator rayQuery(*_pt.getNodeList("rayQuery")); rayQuery.isValid(); rayQuery++)
    {
        // determine the ray query window, viewport, scene & camera name
        String windowName = rayQuery->getChildNodeValue("window", String::EMPTY);
        uint32_t viewportZOrder = rayQuery->getChildNodeValueT<uint32_t>("viewport", ComGfx::IViewport::DEFAULT_ZORDER);
        String sceneName = rayQuery->getChildNodeValue("scene", String::EMPTY);
        String cameraName = rayQuery->getChildNodeValue("camera", String::EMPTY);

        // retrieve the ray query window, viewport, scene & camera
        ComGfx::IWindow * iWindow = getWindow(/*windowName*/);
        ComGfx::IViewport * iViewport = iWindow->getViewport(viewportZOrder);
        ComGfx::IScene * iScene = getScene(sceneName);
        ComGfx::ICamera * iCamera = iScene->getCamera(cameraName);

        // create the rayQuery
        ComGfx::IRayQuery * iRayQuery 
            = iScene->createRayQuery(rayQuery->getValue(), rayQuery->getChildNodeValueT<uint32_t>("queryFlags"),
                                        iCamera, iViewport);
    }

    // create gui systems
    for (PropertyTree::NodeList::ConstIterator guiSystem(*_pt.getNodeList("guiSystem")); guiSystem.isValid(); guiSystem++)
    {
        // determine the gui system window & scene name
        String windowName = guiSystem->getChildNodeValue("window", String::EMPTY);
        String sceneName = guiSystem->getChildNodeValue("scene", String::EMPTY);

        // retrieve the gui system window & scene
        ComGfx::IWindow * iWindow = getWindow(/*windowName*/);
        ComGfx::IScene * iScene = getScene(sceneName);

        // create the gui system
        ComGfx::IGuiSystem * iGuiSystem = 
            createGuiSystem(guiSystem->getValue(), iWindow, iScene);

        // show defined guis
        for (PropertyTree::NodeList::ConstIterator gui(*guiSystem->getChildNodeList("showGui")); gui.isValid(); gui++)        
            iGuiSystem->showGui(gui->getValue(), true);

        // check & process the mouse sub-properties
        PropertyTree::Node * mouse = guiSystem->findChildNode("mouse");
        if (mouse != NULL)
        {            
            iGuiSystem->setMouseImage(mouse->getChildNodeValue("image"));
            Vector2 pos = mouse->getChildNodeValueT("position", Vector2::ZERO);
            iGuiSystem->setMousePosition(pos);
            iGuiSystem->showMouse(mouse->getValue().parse<bool>());
        }
    }
}
//-----------------------------------------------------------------------------
dylab::Simulation * dylab::Ogre_Gfx::getParentSimulation()
{
    return mParentSimulation;
}
//-----------------------------------------------------------------------------
const dylab::Simulation * dylab::Ogre_Gfx::getParentSimulation() const
{
    return mParentSimulation;
}
//-----------------------------------------------------------------------------
dylab::uint32_t dylab::Ogre_Gfx::getCurrentFrameNumber() const
{
    if (mRoot == NULL)
        throw EInvalidState("Ogre::Root is not created.");

    return mRoot->getCurrentFrameNumber();
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IWindow * 
dylab::Ogre_Gfx::createWindow(const String & _title)
{
	if (mDefaultWindow != NULL)
		throw EInvalidState("Only one window is supported so far!");

    // create the window
    mDefaultWindow = new Window(_title, this);
    DYLAB_ASSERT(mDefaultWindow != NULL);

    // return the created window
    return mDefaultWindow;
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IWindow * 
dylab::Ogre_Gfx::getWindow()
{
	DYLAB_ASSERT(mDefaultWindow != NULL);
    return mDefaultWindow;
}
//-----------------------------------------------------------------------------
const dylab::Ogre_Gfx::IWindow * 
dylab::Ogre_Gfx::getWindow() const
{
	DYLAB_ASSERT(mDefaultWindow != NULL);
	return mDefaultWindow;
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IScene * 
dylab::Ogre_Gfx::createScene(const String & _name)
{
    // create the scene
    Scene * scene = new Scene(_name, this);
    DYLAB_ASSERT(scene != NULL);

    // store the scene in the map
    mSceneMap.insert(_name, scene);

    // store the scene as the default, if it is the first one
    if (mDefaultScene == NULL)
        mDefaultScene = scene;

    // return the created scene
    return scene;
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IScene * 
dylab::Ogre_Gfx::getScene(const String & _name)
{
    if (_name == DEFAULT_NAME)
    {
        DYLAB_ASSERT(mDefaultScene != NULL);
        return mDefaultScene;
    }
    else
        return mSceneMap.get(_name);
}
//-----------------------------------------------------------------------------
const dylab::Ogre_Gfx::IScene * 
dylab::Ogre_Gfx::getScene(const String & _name) const
{
    if (_name == DEFAULT_NAME)
    {
        DYLAB_ASSERT(mDefaultScene != NULL);
        return mDefaultScene;
    }
    else
        return mSceneMap.get(_name);
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::renderScene(const String & _name)
{
    uint32_t elapsedMS = static_cast<uint32_t>(mTimer.getElapsedMiliseconds());
    mTimer.reset();

    // prepare the scen for rendering
    dynamicCast<Scene>(getScene(_name))->prepareForRendering();

    // render actual frame
    mRoot->renderOneFrame();

    // push the event to the queue
    mParentSimulation->getGfxEventDispatcher()->sendEvent(GfxEvent(GfxEvent::FRAME_RENDERED, mRoot->getCurrentFrameNumber(), elapsedMS));
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::destroyScene(const String & _name)
{
    String name = _name;
    
    if (name == DEFAULT_NAME)
    {
        DYLAB_ASSERT(mDefaultScene != NULL);
        name = mDefaultScene->getOgreSceneMgr()->getName();
    }

    Ogre::Root::getSingleton().destroySceneManager(mSceneMap.get(name)->getOgreSceneMgr());
    mSceneMap.remove(name);
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IGuiSystem * 
dylab::Ogre_Gfx::createGuiSystem(const String & _name, IWindow * _parentWindow, IScene * _parentScene)
{
    // determine the parent window
    IWindow * window = _parentWindow;
    if (window == NULL)
    {
        DYLAB_ASSERT(mDefaultWindow != NULL);
        window = mDefaultWindow;
    }

    // determine the parent scene
    IScene * scene = _parentScene;
    if (scene == NULL)
    {
        DYLAB_ASSERT(mDefaultScene != NULL);
        scene = mDefaultScene;
    }

    // create the guiSystem
    GuiSystem * guiSystem = new GuiSystem(_name, dynamicCast<Window>(window), dynamicCast<Scene>(scene));
    DYLAB_ASSERT(guiSystem != NULL);

    // store the guiSystem in the map
    mGuiSystemMap.insert(_name, guiSystem);

    // store the guiSystem as the default, if it is the first one
    if (mDefaultGuiSystem == NULL)
        mDefaultGuiSystem = guiSystem;

    // return the created guiSystem
    return guiSystem;
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IGuiSystem * 
dylab::Ogre_Gfx::getGuiSystem(const String & _name)
{
    if (_name == DEFAULT_NAME)
    {
        DYLAB_ASSERT(mDefaultGuiSystem != NULL);
        return mDefaultGuiSystem;
    }
    else
        return mGuiSystemMap.get(_name);
}
//-----------------------------------------------------------------------------
const dylab::Ogre_Gfx::IGuiSystem * 
dylab::Ogre_Gfx::getGuiSystem(const String & _name) const
{
    if (_name == DEFAULT_NAME)
    {
        DYLAB_ASSERT(mDefaultGuiSystem != NULL);
        return mDefaultGuiSystem;
    }
    else
        return mGuiSystemMap.get(_name);
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::destroyGuiSystem(const String & _name)
{
    String name = _name;
    
    if (name == DEFAULT_NAME)
    {
        DYLAB_ASSERT(mDefaultGuiSystem != NULL);
        name = mDefaultGuiSystem->getName();
    }

    mGuiSystemMap.remove(name);
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::IMeshManager * dylab::Ogre_Gfx::getMeshManager()
{
    return mMeshManager;
}
//-----------------------------------------------------------------------------
const dylab::Ogre_Gfx::IMeshManager * dylab::Ogre_Gfx::getMeshManager() const
{
    return mMeshManager;
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::setRenderSystemParam(const String & _renderSys, const String & _paramName, const String & _paramValue)
{
    // check the ogre root object
    DYLAB_ASSERT(mRoot != NULL);

    // retrieve the specified render system        
    Ogre::RenderSystem * rs = mRoot->getRenderSystemByName(_renderSys);
    if (rs == NULL)
        throw EDoesNotExists(String::fmt("Ogre render system '%1%' not found!", _renderSys));

    // set the supplied render system parameter
    rs->setConfigOption(_paramName, _paramValue);         
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::setActiveRenderSystem(const String & _renderSys)
{
    // check the ogre root object
    DYLAB_ASSERT(mRoot != NULL);

    // retrieve the specified render system        
    Ogre::RenderSystem * rs = mRoot->getRenderSystemByName(_renderSys);
    if (rs == NULL)
        throw EDoesNotExists(String::fmt("Ogre render system '%1%' not found!", _renderSys));

    // select the active render system
    mRoot->setRenderSystem(rs);
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::addResourceArchive(const String & _section, const String & _type, const String & _archive)
{
    // add resource group location
    Ogre::ResourceGroupManager::getSingleton().addResourceLocation(_archive, _type, _section);    
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::initAllResourceGroups()
{
	// Initialise, parse scripts etc
    Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}
//-----------------------------------------------------------------------------